package uk.ac.jisc.depositmo.widgets;

/**
 * Copyright (c) 2011, University of Southampton
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * # Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 * # Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation 
 *     and/or other materials provided with the distribution.
 * # Neither the name of the University of Southampton nor the names of its
 *     contributors may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *  
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.File;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

import uk.ac.jisc.depositmo.common.FileUtils;

/**
 * Instances of this class represent a table.
 * @author Mark Borkum <mark.borkum@gmail.com>
 *
 */
public abstract class FileTransferTable extends Composite {
	private static DecimalFormat DECIMAL_FORMAT;
	
	static {
		DECIMAL_FORMAT = new DecimalFormat();
		DECIMAL_FORMAT.setMaximumFractionDigits(2);
	}
	
	private static final FileTransferTableColumn[] TABLE_COLUMNS = new FileTransferTableColumn[] {
		new FileTransferTableColumn(SWT.NONE, "Filename", 300) {
			public Image toImage(Display display, File file) {
				return new Image(display, FileUtils.getProgramImageData(file));
			}
			
			public String toText(File file) {
				return file.getAbsolutePath();
			}
		},
		new FileTransferTableColumn(SWT.CENTER, "Content-Type", 150) {
			public Image toImage(Display display, File file) {
				return null;
			}
			
			public String toText(File file) {
				return FileUtils.getContentType(file);
			}
		},
		new FileTransferTableColumn(SWT.CENTER, "Size", 100) {
			public Image toImage(Display display, File file) {
				return null;
			}
			
			public String toText(File file) {
				long size = file.length();
				
				if (size < (1024 * 1024)) {
					return (file.length() / 1024) + " KiB";
				} else {
					return DECIMAL_FORMAT.format(Float.valueOf(Float.valueOf(Long.valueOf(file.length()) / 1024) / 1024)) + " MiB";
				}
			}
		},
		new FileTransferTableColumn(SWT.CENTER, "Last Modified", 250) {
			public Image toImage(Display display, File file) {
				return null;
			}
			
			public String toText(File file) {
				return new Date(file.lastModified()).toString();
			}
		}
	};
	
	private List<File> fileList;
	
	/**
	 * Constructs a new instance of this class given its parent. 
	 * @param parent  a widget to contain this instance
	 */
	public FileTransferTable(Composite parent) {
		this(parent, new File[0]);
	}
	
	/**
	 * Constructs a new instance of this class given its parent and an initial list of files.
	 * @param parent  a widget to contain this instance
	 * @param files  the initial list of files
	 */
	public FileTransferTable(Composite parent, File[] files) {
		super(parent, SWT.NONE);
		
		this.setFileList(new LinkedList<File>());
		
		/**
		 * 1. Construct widgets.
		 */
		
		final Group group = new Group(this, SWT.NONE);
		
		final Button addFilesButton = new Button(group, SWT.PUSH);
		addFilesButton.setText("Add");
		
		final Button removeSelectedFilesButton = new Button(group, SWT.PUSH);
		removeSelectedFilesButton.setText("Remove");
		
		final Button removeAllFilesButton = new Button(group, SWT.PUSH);
		removeAllFilesButton.setText("Clear");
		
		final Table table = new Table(group, SWT.FULL_SELECTION | SWT.HIDE_SELECTION | SWT.MULTI);
		table.setHeaderVisible(true);
		table.setLinesVisible(true);
		table.setToolTipText("Drop file(s) here");
		
		for (FileTransferTableColumn fileTransferTableColumn : TABLE_COLUMNS) {
			@SuppressWarnings("unused")
			final TableColumn tableColumn = fileTransferTableColumn.toTableColumn(table);
		}
		
		/**
		 * 2. Set layout.
		 */
		
		setLayout(new GridLayout());
		
		group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		
		final GridLayout layout = new GridLayout();
		layout.makeColumnsEqualWidth = true;
		layout.numColumns = 3;
		group.setLayout(layout);
		
		addFilesButton.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
		
		removeSelectedFilesButton.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
		
		removeAllFilesButton.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
		
		final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
		layoutData.horizontalSpan = 3;
		layoutData.minimumHeight = table.getItemHeight() * 6;
		table.setLayoutData(layoutData);
		
		table.setLayout(new GridLayout());
		
		/**
		 * 3. Hook listeners.
		 */
		
		final Listener tableSelectedAction = new Listener() {
			public void handleEvent(Event e) {
				addFilesButton.setEnabled(true);
				
				removeSelectedFilesButton.setEnabled(table.getSelectionCount() > 0);
				
				removeAllFilesButton.setEnabled(table.getItemCount() > 0);
				
				group.setText("Files (" + table.getItemCount() + ")");
			}
		};
		
		final Listener addFilesAction = new Listener() {
			public void handleEvent(Event e) {
				File[] files = (File[]) e.data;
				
				if (files.length > 0) {
					List<File> newFileList = new LinkedList<File>();
					
					for (File file : getFileList()) {
						newFileList.add(file);
					}
					
					for (File file : files) {
						if (!newFileList.contains(file) && FileDialog.isValidFile(file)) {
							newFileList.add(file);
						}
					}

					if (newFileList.size() > getFileCount()) {
						Collections.sort(newFileList, new Comparator<File>() {
							public int compare(File a, File b) {
								return a.getAbsolutePath().compareTo(b.getAbsolutePath());
							}
						});
						
						for (TableItem tableItem : table.getItems()) {
							tableItem.dispose();
						}
						
						for (final File file : newFileList) {
							final TableItem tableItem = new TableItem(table, SWT.NONE);
							
							for (int i = 0; i < TABLE_COLUMNS.length; i++) {
								final Image tableItemImage = TABLE_COLUMNS[i].toImage(getDisplay(), file);
								
								if (tableItemImage != null) {
									tableItem.setImage(i, tableItemImage);
								}
								
								tableItem.setText(i, TABLE_COLUMNS[i].toText(file));
							}
							
							tableItem.addListener(SWT.Dispose, new Listener() {
								public void handleEvent(Event e1) {
									getFileList().remove(file);
								}
							});
						}
						
						setFileList(newFileList);
						
						filesModified();
					}
				}
				
				tableSelectedAction.handleEvent(null);
			}
		};
		
		final Listener removeSelectedFilesAction = new Listener() {
			public void handleEvent(Event e) {
				if (table.getSelectionCount() > 0) {
					for (TableItem tableItem : table.getSelection()) {
						tableItem.dispose();
					}
					
					filesModified();
				}
				
				tableSelectedAction.handleEvent(null);
			}
		};
		
		final Listener removeAllFilesAction = new Listener() {
			public void handleEvent(Event e) {
				if (table.getItemCount() > 0) {
					for (TableItem tableItem : table.getItems()) {
						tableItem.dispose();
					}
					
					filesModified();
				}
				
				tableSelectedAction.handleEvent(null);
			}
		};
		
		addFilesButton.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event e) {
				final FileDialog fileDialog = new FileDialog(getShell());
				
				File[] response = fileDialog.open();
				
				if (response.length > 0) {
					final Event event = new Event();
					event.data = response;
					addFilesAction.handleEvent(event);
				}
			}
		});
		
		removeSelectedFilesButton.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event e) {
				removeSelectedFilesAction.handleEvent(null);
			}
		});
		
		removeAllFilesButton.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event e) {
				removeAllFilesAction.handleEvent(null);
			}
		});
		
		table.addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				// Do nothing.
			}

			public void keyReleased(KeyEvent e) {
				if (e.keyCode == SWT.DEL) {
					removeSelectedFilesAction.handleEvent(null);
				}
			}
		});
		
		table.addListener(SWT.Selection, tableSelectedAction);
		
		final DropTarget dropTarget = new DropTarget(table, DND.DROP_DEFAULT | DND.DROP_MOVE);
		
		final FileTransfer fileTransfer = FileTransfer.getInstance();
		
		dropTarget.setTransfer(new Transfer[] { fileTransfer });
		
		dropTarget.addDropListener(new DropTargetAdapter() {
			public void drop(DropTargetEvent e) {
				if (fileTransfer.isSupportedType(e.currentDataType)) {
					String[] absolutePaths = (String[]) e.data;
					
					File[] files = new File[absolutePaths.length];
					
					for (int i = 0; i < absolutePaths.length; i++) {
						files[i] = new File(absolutePaths[i]);
					}
					
					final Event event = new Event();
					event.data = files;
					addFilesAction.handleEvent(event);
				}
			}
		});
		
		/**
		 * 5. Add files
		 */
		
		final Event event = new Event();
		event.data = files;
		addFilesAction.handleEvent(event);
	}

	/**
	 * Sets the receiver's file list.
	 * @param fileList  the new file list
	 */
	private void setFileList(List<File> fileList) {
		this.fileList = fileList;
	}

	/**
	 * Returns the receiver's file list.
	 * @return  the file list
	 */
	private List<File> getFileList() {
		return fileList;
	}
	
	/**
	 * Returns the file at the specified index.
	 * @param index  the index
	 * @return  the file at the specified index
	 */
	public File getFile(int index) {
		return getFileList().get(index);
	}
	
	/**
	 * Returns the number of files in the receiver's file list.
	 * @return  the number of files
	 */
	public int getFileCount() {
		return getFileList().size();
	}
	
	/**
	 * Returns the receiver's files.
	 * @return  the files
	 */
	public File[] getFiles() {
		File[] files = new File[getFileCount()];
		
		return getFileList().toArray(files);
	}
	
	/**
	 * Called when the receiver's list of files is modified.
	 */
	public abstract void filesModified();
}
